// Coded by JackAsser/Instinct

import java.io.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.math.*;

public class Tunnel
{
  private static int EWIDTH=96;
  private static int EHEIGHT=62;
  private static int MY_ZOOM=8;
  private static double MY_FPS=50;
  private Image offscreen=null;
  private static int offsets[] =
  {
    0,0x05,
    0,0x08,
    0,0x0B,
    0,0x0E,
    0,0x11,
    0,0x14,
    0,0x17,
    0,0x1a,
    0,0x1d,
    0,0x20,
    0,0x23,
    0,0x26,
    0,0x29,
    0,0x2c,
    0,0x2f,
    0,0x32,
    0,0x35,
    0,0x38,
    0,0x3b,
    0,0x3e,
    0,0x01,
    1,0x04,
    1,0x07,
    1,0x0a,
    1,0x0d,
    1,0x10,
    1,0x13,
    1,0x16,
    1,0x19,
    1,0x1c,
    1,0x1f,
    1,0x22,
    1,0x25,
    1,0x28,
    1,0x2b,
    1,0x2e,
    1,0x31,
    1,0x34,
    1,0x37,
    1,0x3a,
    1,0x3d,
    2,0x00,
    2,0x03,
    2,0x06,
    2,0x09,
    2,0x0c,
    2,0x0f,
    2,0x12,
    2,0x15,
    2,0x18,
    2,0x1b,
    2,0x1e,
    2,0x21,
    2,0x24,
    2,0x27,
    2,0x2a,
    2,0x2d,
    2,0x30,
    2,0x33,
    2,0x36,
    2,0x39,
    2,0x3c
  };


/*
  private byte texture[][]=
  {
    {0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0},
    {0x0,0x0,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1},
    {0x0,0x0,0x1,0x1,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x1,0x1},    
    {0x0,0x0,0x1,0x1,0x2,0x2,0x3,0x3,0x3,0x3,0x3,0x3,0x2,0x2,0x1,0x1},    
    {0x0,0x0,0x1,0x1,0x2,0x2,0x3,0x3,0x3,0x3,0x3,0x3,0x2,0x2,0x1,0x1},    
    {0x0,0x0,0x1,0x1,0x2,0x2,0x3,0x3,0x3,0x3,0x3,0x3,0x2,0x2,0x1,0x1},
    {0x0,0x0,0x1,0x1,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x1,0x1},
    {0x0,0x0,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1}
  };
 */
 
 /* 
  private byte texture[][]=
  {
    {0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0},
    {0x0,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x0,0x1,0x1,0x1,0x1,0x1,0x1,0x1},
    {0x0,0x1,0x2,0x2,0x2,0x2,0x2,0x1,0x0,0x1,0x2,0x2,0x2,0x2,0x2,0x1},    
    {0x0,0x1,0x2,0x3,0x3,0x3,0x2,0x1,0x0,0x1,0x2,0x3,0x3,0x3,0x2,0x1},    
    {0x0,0x1,0x2,0x3,0x3,0x3,0x2,0x1,0x0,0x1,0x2,0x3,0x3,0x3,0x2,0x1},    
    {0x0,0x1,0x2,0x3,0x3,0x3,0x2,0x1,0x0,0x1,0x2,0x3,0x3,0x3,0x2,0x1},
    {0x0,0x1,0x2,0x2,0x2,0x2,0x2,0x1,0x0,0x1,0x2,0x2,0x2,0x2,0x2,0x1},
    {0x0,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x0,0x1,0x1,0x1,0x1,0x1,0x1,0x1}
  };
  */
  /*
  private byte texture[][]=
  {
    {0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0},
    {0,0,0,1,1,2,2,2,2,2,2,2,2,1,1,0},
    {0,0,1,2,2,3,3,3,3,3,3,3,2,2,1,0},
    {0,1,2,3,3,3,3,3,3,3,3,3,3,2,1,0},
    {1,2,2,2,3,3,3,3,3,3,3,2,2,1,0,0},
    {1,1,2,2,2,2,2,2,2,2,2,2,1,0,0,0},
    {0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0},
    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
  };*/
  
  private byte texture[][]=
  {
    {1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0},
    {1,1,1,1,1,1,0,0,0,0,3,3,3,3,0,0},
    {1,1,1,1,1,0,0,2,0,0,3,3,3,3,0,0},
    {1,1,1,1,0,0,2,2,0,0,3,3,3,3,0,0},
    {1,1,1,0,0,2,2,2,0,0,3,3,3,3,0,0},
    {1,1,0,0,2,2,2,2,0,0,3,3,3,3,0,0},
    {1,0,0,2,2,2,2,2,0,0,3,3,3,3,0,0},
    {0,0,0,0,0,0,0,0,0,0,3,3,3,3,0,0},  
  };
  
  private byte stlars[]=
  {
    0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,
    0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,
    0,0,1,1,1,1,1,0,0,1,1,1,0,0,0,0,
    0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,
    0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,
    0,0,0,1,1,0,1,1,1,0,1,1,1,1,1,0,
    0,0,1,1,0,1,1,0,1,1,0,1,1,1,1,0,
    0,1,1,1,0,1,1,0,1,1,1,0,0,1,1,1,
    0,1,1,1,0,1,1,0,1,1,1,1,0,1,1,1,
    1,1,1,1,0,1,1,0,1,1,1,1,0,0,1,1,
    1,1,1,1,1,0,1,1,0,1,1,1,0,0,0,0,
    1,1,1,1,1,1,0,1,1,0,1,1,0,0,0,0,
    1,1,1,1,1,1,1,0,1,1,1,1,0,0,0,0,
    0,1,1,1,1,1,1,1,0,1,1,0,0,0,0,0,
    0,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,
    0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,
    1,1,1,1,1,0,1,1,1,0,0,0,0,0,0,0,
    1,0,1,1,1,0,1,1,1,0,0,0,0,0,0,0,
    0,0,1,1,1,0,1,1,1,1,0,0,0,0,0,0,
    0,1,1,1,1,1,0,1,1,1,1,0,0,0,0,0,
    0,1,1,1,1,1,0,1,1,1,1,1,0,0,0,0
  };  
  
  private int sourceTexel[][];
  
  private int c64colors[]=
  {
    0x000000,
    0xFFFFFF,
    0x68372B,
    0x70A4B2,
    0x6F3D86,
    0x588D43,
    0x352879,
    0xB8C76F,
    0x6F4F25,
    0x433900,
    0x9A6759,
    0x444444,
    0x6C6C6C,
    0x9AD284,
    0x6C5EB5,
    0x959595
  };
  
  private int colors[]={c64colors[0],c64colors[5],c64colors[2],c64colors[7]};

  public Tunnel()
  {
    sourceTexel=new int[EWIDTH][EHEIGHT];

/*    
    for (int y=0;y<EHEIGHT; y++)
    {
      for (int x=0;x<EWIDTH; x+=4)
      {
        int offset=offsets[y*2+0]*8*64 + (x/12)*64 + ((((x/4)%3) + offsets[y*2+1])&63);
        int val=(((x/4)+(y/4))&1)==0 ? 0x00:0xff;
        System.out.println("lda #"+val+" ;2");
        System.out.println("sta sprites+"+offset+" ;4");
      }
    }    
    System.exit(0);
 */   
    for (int y=0; y<EHEIGHT/2; y++)
    {
      for (int x=0; x<EWIDTH; x++)
      {
        double vx=((double)x)-((double)EWIDTH)/2.0+0;
        double vy=((double)y)-((double)EHEIGHT)/2.0+0;
        double r=1600/(Math.sqrt(vx*vx+vy*vy)+10);
        
        if ((x<8) || (x>=88) || (y<6) || (y>=56))
          r+=0;
        
        if (r<78)
        {
          double t=Math.atan2(vx,vy);//+Math.pow(r-40,2)*0.0005;
          t+=Math.PI;
          t/=Math.PI*2;
          int tx=(int)(r/1);
          int ty=(int)(t*8*4*2);
          tx&=15;
          ty&=7;
          sourceTexel[x][y]=(tx<<3)+ty;
        }
        else
          sourceTexel[x][y]=0xff000000;
          
        sourceTexel[EWIDTH-x-1][EHEIGHT-y-1]=sourceTexel[x][y];
      }
    }
    
    for (int y=0;y<21*2;y++)
      for (int x=0;x<16*2;x++)
        if (stlars[(x/2)+(y/2)*16]==1)
          sourceTexel[x+36][y+10]=0xff000003;
    
    byte[] r=new byte[4];
    byte[] g=new byte[4];
    byte[] b=new byte[4];
    for (int i=0;i<4;i++)
    {
      int c=colors[i];
      r[i]=(byte)((c&0xff0000)>>16);
      g[i]=(byte)((c&0x00ff00)>>8);
      b[i]=(byte)(c&0x0000ff);
    }
    
    IndexColorModel icm=new IndexColorModel(8,4,r,g,b);
    final byte screen[]=new byte[EWIDTH*EHEIGHT];
    final MemoryImageSource mis=new MemoryImageSource(EWIDTH,EHEIGHT,icm,screen,0,EWIDTH);
    mis.setAnimated(true);
    final Image display=Toolkit.getDefaultToolkit().createImage(mis);
    
    final Frame f=new Frame("Display");
    f.setLayout(new BorderLayout());
    
    final Panel cc=new Panel()
    {
      public void paint(Graphics g)
      {
        if (offscreen==null)
          offscreen=createImage(EWIDTH*MY_ZOOM,EHEIGHT*MY_ZOOM);

        if (offscreen!=null)
        {
          Graphics ig=offscreen.getGraphics();
          ig.drawImage(display,0,0,EWIDTH*MY_ZOOM,EHEIGHT*MY_ZOOM,this);
          ig.setColor(Color.BLACK);
          //ig.drawRect(8*MY_ZOOM,6*MY_ZOOM,80*MY_ZOOM-1,50*MY_ZOOM-1);
          g.drawImage(offscreen,0,0,null);
        }      
      }

      public void update(Graphics g)
		{
			paint(g);
		}

		public Dimension getPreferredSize()
		{
			return new Dimension(EWIDTH*MY_ZOOM, EHEIGHT*MY_ZOOM);
		}
    };
    f.add(cc);
    f.setResizable(false);
    f.pack();
    f.setVisible(true);
    f.toFront();
    
    f.addWindowListener(new WindowAdapter()
    {
      public void windowClosing(WindowEvent e)
		{
			System.exit(0);
		}
    });
    
    class TexelQuadruple
    {
      public int[] texels;
      
      public TexelQuadruple()
      {
        texels=new int[4];
      }
      
      public int hashCode()
      {
        return 0;
      }

      public boolean equals(Object o)
      {
        if (o instanceof TexelQuadruple)
        {
          TexelQuadruple ot=(TexelQuadruple)o;
          for (int i=0; i<4; i++)
          {
            if (texels[i]!=ot.texels[i])
              return false;
          }
          return true;
        }
        return false;
      }
    };

    // Find all unique texel quadruple combinations and remeber the offsets
    HashMap texelQuadruples=new HashMap();
    for (int y=0; y<EHEIGHT; y++)
    {
      for (int x=0; x<EWIDTH; x+=4)
      {
        TexelQuadruple tq=new TexelQuadruple();
        for (int t=0; t<4; t++)
          tq.texels[t]=sourceTexel[x+t][y];

        ArrayList al;
        if (texelQuadruples.containsKey(tq))
          al=(ArrayList)texelQuadruples.get(tq);
        else
          al=new ArrayList();
        
        al.add(new Integer((x<<8)+y));
        texelQuadruples.put(tq, al);
      }
    }
    
    // Now, for each texel quadruple, generate source
    StringBuffer source=new StringBuffer();
    Iterator i=texelQuadruples.keySet().iterator();
    int total=0;
    while(i.hasNext())
    {
      TexelQuadruple tq=(TexelQuadruple)i.next();
      ArrayList al=(ArrayList)texelQuadruples.get(tq);
   
      StringBuffer subSource=new StringBuffer();
      int subtotal=0;
      int nrLookups=0;
      int immediate=0;
      for (int j=0; j<4; j++)
      {
        if ((tq.texels[j]&0xff000000)!=0)
          immediate|=(tq.texels[j]&0xff)<<(6-j*2);
      }
      
      boolean first=true;
      for (int j=0; j<4; j++)
      {
        if ((tq.texels[j]&0xff000000)!=0)
          continue;
      
        if (first)
          subSource.append("lda ");
        else
          subSource.append("ora ");
          
        int value=tq.texels[j]&0xff;
      
        int run;
        for (run=0;run+j<4;run++)
          if ( ((tq.texels[run+j]&0xff000000)==0) && ((tq.texels[run+j]&0xff)==value) )
            ;
          else
            break;
              
        subtotal+=4;	// Texture lookup (LDA,x/ORA,x)
        nrLookups++;
        subSource.append("texture"+j+"_"+run+"+"+value+",x ;4\n");
        j+=run-1;

        first=false;
      }

      if (immediate!=0)
      {
        if (first)
          subSource.append("lda #"+immediate+" ;2\n");
        else
          subSource.append("ora #"+immediate+" ;2\n");
        subtotal+=2;
      } 
      
      if (nrLookups==0)	// This texel quadruple never really fetches from the texture => ignore
        continue;
      total+=subtotal;
      
      source.append(subSource);
      
      while(true)
      {
        for (int j=0; j<al.size(); j++)
        {
          Integer pos=(Integer)al.get(j);
          int x=(pos>>8)&0xff;
          int y=pos&0xff;
          int offset=offsets[y*2+0]*8*64 + (x/12)*64 + ((((x/4)%3) + offsets[y*2+1])&63);
          source.append("sta sprites+"+offset+" ;4\n");
        
          total+=4;	// STA
        }
        texelQuadruples.remove(tq);
        i=texelQuadruples.keySet().iterator();

        // It took more than 8 cycles to compile the byte. Flipping a byte takes exactly 8 cycles, so check if there exist a flipped version
        if (subtotal>8)
        {
          TexelQuadruple ftq=new TexelQuadruple();
          for (int t=0; t<4; t++)
            ftq.texels[t]=tq.texels[3-t];

          if (texelQuadruples.containsKey(ftq))
          {
            al=(ArrayList)texelQuadruples.get(ftq);
            tq=ftq;

            source.append("flip ;8\n");
            total+=8;
          }
          else
            break;
          subtotal=0;
        }
        else
          break;
/*
Total cycles: 19326
Effect for two frames: 15872
Grand total (25hz): 35198 / 39312
*/        
      }
    }
    System.err.println("Total cycles: "+total);
    int effectTime=(10*248+5*248+17*248)*2;
    System.err.println("Effect for two frames: "+effectTime);
    System.err.println("Grand total (25hz): "+(total+effectTime)+" / 39312");
    System.out.print(source.toString());
    
    System.exit(0);
    
    final int totalCycles=total;
    
    Thread t=new Thread()
    {
      public void run()
      {
        int frame=0;
        int ddx=-1;
        int ddy=1;
        
        long startTime=Calendar.getInstance().getTimeInMillis();
      
        int dx=0;
        while (true)
        {
          for (int y=0; y<EHEIGHT; y++)
          {
            for (int x=0; x<EWIDTH; x++)
            {
              int texel=sourceTexel[x][y];
              if ((texel&0xff000000)==0)
              {
                texel+=dx;
                int tx=(texel>>3)&0x0f;
                int ty=texel&0x07;
                tx&=15;
                ty&=7;
                screen[x+y*EWIDTH]=texture[ty][tx];
              }
              else
                screen[x+y*EWIDTH]=(byte)(texel&15);
            }
          }          
          mis.newPixels(0,0,EWIDTH,EHEIGHT);
          
          if ((frame&1)==0)
          {
            dx+=ddx+ddy*16;
            if ((frame&0x1f)==0)
            {
              while(true)
              {
                ddx=(int)Math.round(Math.random()*2-1);
                ddy=(int)Math.round(Math.random()*2-1);
                if ((ddx!=0) || (ddy!=0))
                  break;
              }
            }
          }
          
          frame++;
          double frameTime=((double)frame)*1000.0/MY_FPS;
          long pauseTime=(startTime+(long)frameTime) - Calendar.getInstance().getTimeInMillis();
          
          f.setTitle(frame+" ("+frameTime+") "+totalCycles+"c");
          
          if (pauseTime>0)
          {
            try
            {
              sleep(pauseTime);
            }
            catch (InterruptedException e)
            {
              ;
            }
          }
        }
      }
    };
    t.start();
  }

  public static void main(String[] args)
  {
    new Tunnel();
  }
}